
package gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JPanel;

import model.Bearing;
import model.Model;
import model.Trail;
import model.Turtle;

/**
 * Component that handles drawing and displaying of cursors, trails, and world
 * 
 * @author Troy Ferrell
 */
public class Scene extends JPanel implements ComponentListener, Observer 
{
	public final Dimension DEFAULT_SIZE = new Dimension(); 
	
	private Model myModel;
	
	// the width and height ratios for scene:world
	// e.g 2:1 = 2 pixels for every 1 world unit
	private double horzonitalRatio, verticalRatio; 
	private double RATIO_MAX = 8;
	
	public Scene(Model newModel)
	{
		myModel = newModel;
		horzonitalRatio = verticalRatio = 1;
		
		myModel.addObserver( Model.TURTLE_OBSERVER , this);
		myModel.addObserver(Model.WORLD_OBSERVER, this);
		
		repaint();
	}
	
	/**
	 * Scale up representation of world 
	 */
	public void zoomIn()
	{		
		if(horzonitalRatio*2.0 <= RATIO_MAX)
			horzonitalRatio *= 2.0;
		
		if(verticalRatio * 2.0 <= RATIO_MAX)
			verticalRatio *= 2.0;
		
		super.repaint();
	}
	
	/**
	 * Scale down representation of world 
	 */
	public void zoomOut()
	{
		if(horzonitalRatio / 2.0 >= 1.0 / RATIO_MAX)
			horzonitalRatio /= 2.0;
	
		if(verticalRatio / 2.0 >= 1.0 / RATIO_MAX)
			verticalRatio /= 2.0;
	}
	
	/**
	 * Method in charge of drawing scene onto JPanel(aka canvas)
	 * 
	 * @param g - Graphics object passed by java system to help with drawing
	 */
	public void paint(Graphics g)
	{
		drawBackground(g);
		
		drawTrails(g); 
		
		drawCursors(g);
	}

	/**
	 * Draw background image for scene
	 * @param g - graphics object for drawing
	 */
	private void drawBackground(Graphics g) 
	{
		//TODO: error - when zooming out, background doesn't update correctly..
		// if you resize the panel, it will update correctly
		
		BufferedImage bg = myModel.getBackgroundImage();	
		
		Point bgPoint = getOriginTransformation(0, 0);
		Dimension worldSize = myModel.getWorldBounds();
		Dimension bgSize = getSizeTransformation((int)worldSize.getWidth(), (int)worldSize.getHeight());
		
		g.drawImage(bg, bgPoint.x - bgSize.width/2, bgPoint.y - bgSize.height/2,
				bgSize.width, bgSize.height, Color.GRAY, null);
	}

	/**
	 * Draw cursor objects with transformations
	 * 
	 * @param g - graphics object for drawing
	 */
	private void drawCursors(Graphics g) 
	{	
		Graphics2D g2 = (Graphics2D)g;
		
		Collection<Turtle> turtles = this.myModel.getAllTurtles();
		
		for(Turtle t : turtles)
		{
			BufferedImage turtleImg = t.getImage();
			if(turtleImg == null || !t.visibility)
				// TODO: handle exception
				continue;
			
			Bearing turtleBearing = t.getBearing();
			
			Point drawPoint = getOriginTransformation(turtleBearing.getX(), turtleBearing.getY());
			Dimension drawSize = getSizeTransformation(turtleImg.getWidth(), turtleImg.getHeight());
			
			//Save transform of current system for later
			AffineTransform originalTransform = g2.getTransform();
			
			g2.translate(drawPoint.x - drawSize.width/2, drawPoint.y - drawSize.height/2);
			
			g2.rotate( -turtleBearing.getDirection()*(Math.PI/180.0) , drawSize.width/2, drawSize.height/2);
			g2.drawImage(turtleImg, 0, 0,
					drawSize.width, drawSize.height, null );
			
			//clear above transforms for new drawing by reestablishing old transform set
			g2.setTransform(originalTransform);
		}
	}

	/**
	 * Draw trails created by cursors
	 * 
	 * @param g - graphics object for drawing
	 */
	private void drawTrails(Graphics g) 
	{
		Graphics2D g2 = (Graphics2D)g;
		Collection<Trail> trails = this.myModel.getAllTrails();
		
		for(Trail tr : trails)
		{
			ArrayList<Point> coordinatePoints = new ArrayList<Point>(tr.getPathCoordinates());
			
			if(coordinatePoints.size() < 2)
				continue;
			
			Point point1 = coordinatePoints.get(0);
			for(int i = 1 ; i < coordinatePoints.size(); i++)
			{
				Point point2 = coordinatePoints.get(i);
				
				Point transformPoint1 = getOriginTransformation(point1.x, point1.y);
				Point transformPoint2 = getOriginTransformation(point2.x, point2.y);
			
				g2.setStroke(new BasicStroke(tr.myPen.getThickness()));
				
				g2.drawLine(transformPoint1.x, transformPoint1.y, transformPoint2.x, transformPoint2.y);
				
				point1 = point2;
			}
		}
	}
	
	/**
	 * Method in charge of translating coordinates of model objects 
	 * into coordinates that work with java's graphic architecture
	 * 
	 * @param x, y
	 * @return Point object with translated coordinates
	 */
	private Point getOriginTransformation(int x, int y)
	{
		// pointRatio : unitRatio (e.g 4:1 = 4 pixels for every 1 world unit)
		// so aspectRatio = pointRatio / unitRatio
		return new Point(this.getWidth()/2 + (int)(x*horzonitalRatio), this.getHeight()/2 + (int)(y*verticalRatio));
	}
	
	/**
	 * Function that transforms size of model object based on
	 * aspect ratio of view(scaling)
	 * 
	 * @param width
	 * @param height
	 * @return Dimension object with size transformations
	 */
	private Dimension getSizeTransformation(int width, int height)
	{
		// pointRatio : unitRatio (e.g 4:1 = 4 pixels for every 1 world unit)
		// so aspectRatio = pointRatio / unitRatio
		return new Dimension((int)(width*horzonitalRatio), (int)(height*verticalRatio));
	}
	
	
	@Override
	/**
	 * Observer update method...repaint scene due to changes in model
	 */
	public void update(Observable arg0, Object arg1) 
	{
		super.repaint();
	}

	@Override
	public void componentHidden(ComponentEvent arg0) {}
	public void componentMoved(ComponentEvent arg0) {}
	public void componentResized(ComponentEvent arg0) 
	{
		super.repaint();
	}
	public void componentShown(ComponentEvent arg0) {}
}
